/**
 * 原生模块
 */
let fs = require('fs')
let url = require('url')
let path = require('path')
let http = require('http')
let mime = require('mime-types')
/**
 * 自定义模块
 */
let utils = require('./utils')

let { promisify, renderFiles, openBrowser } = utils

let exists = promisify(fs.exists)
let lstat = promisify(fs.lstat)
let realpath = promisify(fs.realpath)
let readdir = promisify(fs.readdir)
let readFile = promisify(fs.readFile)

let port = 4000
let cwd = process.cwd()

let defaultContentType = 'text/plain; charset=utf-8'

main()

/**
 * 主程序
 * TODO: search
 */
function main() {
  let server = http.createServer(handleRequest)

  // 监听端口后，打开浏览器
  server.listen({ port }, () => {
    // openBrowser(`http://localhost:${port}`)
  })
}

// create static server
http
  .createServer(
    
  )
  .listen({ port: 4600 })

let a = createReader({
  fs,
  root: 'xxxx',
  // renderFile: () => {},
  // renderDirectory: directory => {
  //   res.end('just render')
  // },
  // handleFile: (res, filename) => {
  //   res.end('file://' + filename)
  // }
})

a.handle('/xxxx') // { type: 'directory|file|notfound', data: { mime: '', content: '', files" [] } }

/**
 * path, /\
 * static-server {
 *   url-resolver {
 *     clip
 *     xxx
 *   }
 *   fs-reader
 *   renderer
 * }
 */

let static = (options) => {
  let reader = createReader(options)

  return async (req, res, next) => {
    let url = resolver.cut(req.url, options.vd)
    let result = await reader.read(url)

    let files = result.data.files(file => resolver.resolve(file, options.vd))

    // renderer
    if (result.type === 'NOTFOUND') {
      next()
    } else if (result.type === 'html') {
      res.set('Content-Type', result.data.mime)
      res.send(options.render(result))
    } else if (result.type === 'file') {
      res.set('Content-Type', result.data.mime)
      res.end(result.data.content)
    }
  }
}



function static(options) {
  let { vd } = options

  return (req, res, next) => {
    req.vd = vd
    res.renderDirectory = options.renderDirectory
    res.handleFile = options.handleFile

    handleRequest(req, res)
  }
}

async function handleRequest(req, res) {
  let ctx = createContext(req, res)

  let { pathname, vd } = ctx
  let filename = path.join(cwd, pathname)

  let fileInfo = await readFileInfo(filename)
  let { isExist, isDirectory, isFile } = fileInfo

  if (!isExist) {
    ctx.render('404 Not Found')
  } else if (isDirectory) {
    if (res.renderDirectory) {
      return res.renderDirectory(res)
    }
    let html = await renderDirectory({ pathname, filename, vd })

    ctx.render(html)
  } else if (isFile) {
    ctx.sendFile(filename)
  }
}

function createContext(req, res) {
  let render = html => {
    res.setHeader('Content-Type', 'text/html; charset=utf-8')
    res.end(html)
  }

  let sendFile = async filename => {
    if (res.handleFile) {
      return res.handleFile(res, filename)
    }
    let contentType =
      mime.contentType(path.basename(filename)) || defaultContentType
    let fileContent = await readFile(filename)

    res.setHeader('Content-Type', contentType)
    res.end(fileContent)
  }

  let { vd } = req

  let urlObj = url.parse(req.url)

  if (urlObj.pathname) {
    // 解码中文路径
    urlObj.pathname = decodeURIComponent(urlObj.pathname)

    if (vd) {
      urlObj.pathname = urlObj.pathname.replace(vd, '')
    }
  }

  return {
    ...urlObj,
    vd,
    req,
    res,
    render,
    sendFile
  }
}

async function readFileInfo(filename) {
  let isExist = await exists(filename)

  if (!isExist) return { isExist }

  let fileInfo = await getFileInfo(filename)

  return {
    isExist,
    ...fileInfo
  }
}

async function renderDirectory({ pathname, filename, vd }) {
  let files = await readdir(filename)

  if (pathname !== '/' && pathname !== vd) {
    files = ['..', ...files]
  }

  let fileMapList = await createfileMapList(files, pathname, vd)

  let breadCrumbList = createBreadCrumbList(pathname, vd)

  let html = await renderFiles({ breadCrumbList, fileMapList, pathname, vd })

  return html
}

async function createfileMapList(files, pathname, vd) {
  let fileMapList = await Promise.all(
    files.map(async name => {
      let href = createHref({ name, pathname })

      let clzName = await createClassName({ href })

      return {
        name, // 文件名称
        // a 标签链接
        // 补充虚拟目录，虚拟路径 ifNeeded
        href: vd ? path.join(vd, href) : href,
        clzName // a 标签 class name
      }
    })
  )

  return fileMapList
}

/**
 * 方法名字怎么取
 * TODO: classnames 自己实现
 */
async function createClassName({ href }) {
  let filename = path.join(cwd, href)
  let { isFile, isDirectory } = await getFileInfo(filename)

  let clzName = 'icon'

  if (isDirectory) {
    clzName += ' icon-directory'
  } else if (isFile) {
    let contentType = mime.lookup(href)

    if (contentType) {
      clzName += ' icon-' + contentType.replace(/[.|/]/g, '-')
    } else {
      clzName += ' icon-default'
    }
  }

  return clzName
}

/**
 * 方法名字怎么取
 */
function createHref({ name, pathname }) {
  let href = path.join(pathname, name)

  // 上一级目录链接
  if (name === '..') {
    href = path.dirname(pathname)
  }

  return href
}

let mapValue = (value, index, array) => {
  let newValue = value

  for (let i = index - 1; i >= 0; i--) {
    newValue = array[i] + newValue
  }

  return { href: newValue, value: value.slice(1) }
}

function createBreadCrumbList(pathname, vd) {
  let breadCrumbList = pathname
    .split('/')
    .filter(Boolean)
    .map(_ => '/' + _)
    .map(mapValue)
    .map((item, index, array) => {
      if (index === array.length - 1) {
        return {
          ...item,
          href: 'javascript:;'
        }
      }

      let nextItem = { ...item }

      if (vd) {
        nextItem.href = path.join(vd, item.href)
      }

      return nextItem
    })

  breadCrumbList.unshift({ href: '/', value: '~' })

  return breadCrumbList
}

async function getFileInfo(filename) {
  let stat = await lstat(filename)
  let isFile = stat.isFile()
  let isDirectory = stat.isDirectory()

  if (stat.isSymbolicLink()) {
    let realFilename = await realpath(filename)
    return getFileInfo(realFilename)
  }

  return {
    isFile,
    isDirectory
  }
}
